package com.yeziji.security.utils;

import com.google.common.io.BaseEncoding;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;

import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Objects;

/**
 * @author hwy
 * @since 2024/11/18 23:14
 **/
public class MfaUtils {
    /**
     * 校验 TOTP 码
     *
     * @param input 待校验码
     * @param securityKey 对应的加密字符串
     * @return {@link Boolean} 校验结果
     */
    public boolean validate(String input, String securityKey) {
        try {
            return Objects.equals(generateTOTP(securityKey), input);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 生成 TOTP 码
     *
     * @param secretKey 对应的 secretKey
     * @return {@link String} 返回校验码
     */
    public static String generateTOTP(String secretKey) {
        // 默认 30 秒
        byte[] decodedKey = BaseEncoding.base32().decode(secretKey);
        long timeWindow = Instant.now().getEpochSecond() / 30;

        byte[] data = ByteBuffer.allocate(8).putLong(timeWindow).array();

        try {
            HMac hmac = new HMac(new SHA1Digest());
            hmac.init(new KeyParameter(decodedKey));
            hmac.update(data, 0, data.length);

            byte[] hash = new byte[hmac.getMacSize()];
            hmac.doFinal(hash, 0);

            int offset = hash[hash.length - 1] & 0x0F;
            int binaryCode = ((hash[offset] & 0x7F) << 24)
                    | ((hash[offset + 1] & 0xFF) << 16)
                    | ((hash[offset + 2] & 0xFF) << 8)
                    | (hash[offset + 3] & 0xFF);

            // 6 代表验证码长度
            int otp = binaryCode % (int) Math.pow(10, 6);
            return String.format("%0" + 6 + "d", otp);
        } catch (Exception e) {
            throw new RuntimeException("Error generate TOTP", e);
        }
    }

    /**
     * 创建 otp 链接
     *
     * @param accountName 创建用户
     * @param secret 创建密钥
     * @return {@link String} 返回 otpauth 链接
     */
    public static String generateOtpPath(String issuer, String accountName, String secret) {
        try {
            return "otpauth://totp/"
                    + URLEncoder.encode(accountName, StandardCharsets.UTF_8)
                    + "?secret=" + secret
                    + "&issuer=" + URLEncoder.encode(issuer, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("Error generate otpauth URL", e);
        }
    }
}
